<?php

namespace zhuli\core;

use ReflectionClass;
use Exception;

class Di implements DiInterface
{
    private $depend = [];

    private $dependSingleton = [];

    private $singletonCache = [];

    public function set($class, $args = [])
    {
        $this->depend[$class] = $args;
        return $this;
    }

    public function setSingleton($class, $args = [])
    {
        $this->dependSingleton[$class] = $args;
        return $this;
    }

    public function get($class, $args = [])
    {
        $refClass = new ReflectionClass($class);

        if ($refClass->isInterface()) { // 是接口
            $implClassName = $refClass->getName();
            if (!isset($this->depend[$implClassName])) {
                throw new Exception("Not found implement: $implClassName");
            }
            $refClass = new ReflectionClass($this->depend[$implClassName]);
        }

        $constructor = $refClass->getConstructor();
        $wantGetClassName = $refClass->getName();

        if (
            isset($this->dependSingleton[$wantGetClassName]) &&
            isset($this->singletonCache[$wantGetClassName])
        ) {
            return $this->singletonCache[$wantGetClassName];
        }

        $wantGetObj = null;
        if ($constructor && $params = $constructor->getParameters()) {
            $callParams = [];

            foreach ($params as $position => $param) {
                $dependClass = $param->getClass();

                if ($dependClass instanceof ReflectionClass) {
                    $dependClassName = $dependClass->getName();

                    if (isset($this->depend[$dependClassName])) {
                        $callParams[$position] = $this->get($dependClassName, $this->depend[$dependClassName]);
                    } else {
                        $callParams[$position] = $this->get($dependClassName);
                    }

                } else {
                    $paramName = $param->getName();
                    if (isset($args[$paramName])) {
                        $callParams[$position] = $args[$paramName];
                    } elseif (isset($this->depend[$wantGetClassName])) {
                        if (isset($this->depend[$wantGetClassName][$paramName])) {
                            $callParams[$position] = $this->depend[$wantGetClassName][$paramName];
                        }
                    } else {
                        $callParams[$position] = $param->getDefaultValue();
                    }
                }
            }

            $wantGetObj = $refClass->newInstanceArgs($callParams);
        } else {
            $wantGetObj = $refClass->newInstance();
        }

        if (isset($this->dependSingleton[$wantGetClassName])) {
            $this->singletonCache[$wantGetClassName] = $wantGetObj;
        }
        return $wantGetObj;
    }
}
